昨天我們透過客製化 ViewSet 讓他在不同的 action 中回傳不同序列化,達到修改不同傳入傳出的格式。今天前端提了一個需求:「我想要有一個 API 讓我呼叫後如果原本是已完成,就修改成未完成,如果是未完成,就修改成已完成。」
我們來整理一下前端的需求,需要有一個新的 API 呼叫後需要反轉 is_finish 的狀態。
/api/todo/tasks/{task_id}/status
(裡面的 task_id 會動態替換表示要修改的任務)以上就是這次的需求讓我們開始改吧!
讓我們打開 `` 一起把內容修改成下方這個樣子
-from rest_framework import viewsets
+from rest_framework import decorators, response, viewsets
from server.app.todo import models as todo_models
from server.app.todo import serializers as todo_serializers
class TaskViewSet(viewsets.ModelViewSet):
queryset = todo_models.Task.objects.all()
serializer_class = todo_serializers.TaskSerializer
def get_serializer_class(self):
if self.action == "create":
return todo_serializers.TaskCreateSerializer
return super().get_serializer_class()
+ @decorators.action(methods=["patch"], detail=True)
+ def status(self, request, pk):
+ task = self.get_object()
+
+ serializer = self.get_serializer(
+ task,
+ data={"is_finish": not task.is_finish},
+ partial=True,
+ )
+ serializer.is_valid(raise_exception=True)
+ serializer.save()
+
+ return response.Response(serializer.data)
class TagViewSet(viewsets.ModelViewSet):
queryset = todo_models.Tag.objects.all()
serializer_class = todo_serializers.TagSerializer
現在這個檔案我們在 TaskViewSet 裡面新增了一個 status 的方法,並幫他裝上 action 裝飾器,告訴 ViewSet 說這是一個新增的路由,url 的名稱再不特別指定的情況下會使用方法的名稱,這邊會是 status 所以等等路由會變成 /api/todo/tasks/{task_id}/status
,那 ViewSet 怎麼知道要幫我們加上 task_id 呢?是因為我們在 action 裝飾器中有加上 detail=True 的設定,告訴他我們需要在 url 中指定 id 才能呼叫到這個路由。
而 status 方法的參數,第一個是 self 這個是 Python 的方法都需要收的參數代表物件本身,接著是 request 是 view 規定要收的參數,最後的 pk 是因為我們設定 detail=True
所以需要收的參數,代表使用者在 url 中指定的 ID。
另一個需求是讓這個路由需要使用 patch 方法呼叫,我們是在 action 裝飾器中設定 methods=["patch"]
所以 ViewSet 才會知道使用者必須使用 PATCH 方法呼叫。
接著讓我們看看這個方法中到底做了什麼處理,首先是呼叫了 get_object
這個 ViewSet 提供的方法,他會根據使用者在 url 中所提供的 id 到資料庫中將對應的物件撈取出來,讓我們可以獲得使用者指定的任務。
再來是 get_serializer
這個一樣是 ViewSet 提供的方法,讓我們獲取對的序列化(他會根據 get_serializer_class
回傳的 class 決定你會拿到哪個)並且將 task 放入其中,代表的是要更新這個任務,同時提供 data 指定要更新的欄位以及值,這邊更新的是 is_finish 欄位並是反轉現有的值,最後傳入的 partial 意思是告訴序列化,我們這次的更新只會傳入部分的欄位,避免序列化誤以為我們少傳入欄位而報錯。
完成序列化的初始化後,我們呼叫了 is_valid
來判斷序列化是否有錯誤(raise_exception 是告訴序列化如果有錯誤產生的話直接引法 exception),如果沒有錯誤就呼叫 save
將資料存入資料庫。
方法的錯後將序列化的資料回傳給使用者,這樣就完成了!
現在你可以打開 Postman 用 PATCH 方法呼叫 http://127.0.0.1:8000/api/todo/tasks/{task_id}/status(記得替換 {task_id} 不用傳入任何 HTTP Body 就可以看到 is_finish 不斷變動了。
今天我們在 ViewSet 中額外增加了一個路徑了。結束前別忘了檢查一下今天的程式碼有沒有問題,並排版好喔。
ruff check --fix .
black .
pyright .
明天讓我們來講講權限相關的東西吧!要開始為 API 加上驗證與權限了,不然大家都可以亂使用我們的 API 了。
P.S. 今天的檔案更新可以參考我的 Git Commit 大家可以搭配服用